package com.advice;

import com.annotation.Encrypted;
import com.fasterxml.jackson.annotation.JsonView;
import com.utils.AesUtil;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import sun.misc.IOUtils;

import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

import static com.common.Constants.AES_KEY;

/**
 * 用于解密 AES 加密的一个切面
 * 大致流程是 找到一个 {@link HandlerMethodArgumentResolver} 之后
 * 1、{@link HandlerMethodArgumentResolver} 调用 advice 的前置切面
 * 2、{@link HandlerMethodArgumentResolver} 自己进行解析
 * 3、{@link HandlerMethodArgumentResolver} 调用 advice 的后置切面
 *
 * 这里的切面不是经典的 AOP 切面 也不是 jdk 或者 CGLib 代理
 * 而是类似钩子函数的静态切面
 */
public class RequestBodyDecryptAdvice implements RequestBodyAdvice {

    /**
     * 只要方法参数上有 {@link Encrypted} 就能触发此切面
     * @param methodParameter
     * @param targetType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.getParameterAnnotation(Encrypted.class)!=null;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

        final String encrypted = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);

        try {
            // 解密操作
            final String decrypted = AesUtil.decrypt(encrypted, AES_KEY);

            return new SimpleHttpInputMessage(inputMessage.getHeaders(), decrypted);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return null;
    }

    static class SimpleHttpInputMessage implements HttpInputMessage {

        private final HttpHeaders headers;
        private final String decrypted;

        public SimpleHttpInputMessage(HttpHeaders headers, String decrypted) {
            this.headers = headers;
            this.decrypted = decrypted;
        }

        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8));
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}
